Conversation
Walkthrough온보딩 UI 리디자인 적용: ProgressBar를 그라데이션에서 단색으로 변경하고 TopBar에 진행바 표시 제어 추가. 온보딩 API를 v2로 전환하고 사용자 온보딩 조회(getUserOnBoarding) 엔드포인트 및 DTO/데이터 흐름(데이터층→도메인→뷰모델)을 추가. 프레젠테이션에 Intro/기존 요약 템플릿과 NinePatch 유틸 추가. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as 사용자
participant S as OnBoardingScreen
participant VM as OnBoardingViewModel
participant FP as FetchUserProfileUseCase
participant GU as GetUserOnBoardingUseCase
participant R as OnBoardingRepository
participant DS as OnBoardingDataSource
participant API as OnBoardingService (v2)
U->>S: 화면 진입
S->>VM: init(onBoardingArg)
VM->>FP: 사용자 프로필 조회
FP-->>VM: 닉네임 반환
alt SetType == NEW
VM-->>S: LoadIntroSuccess(userName)
note right of S: Intro 템플릿 표시
else SetType == RESET
VM->>GU: getUserOnBoarding()
GU->>R: getUserOnBoarding()
R->>DS: getUserOnBoarding()
DS->>API: GET /api/v2/onboardings
API-->>DS: GetUserOnBoardingResponse
DS-->>R: Result<GetUserOnBoardingResponse>
R-->>GU: Result<List<Pair<String,List<String>>>>
GU-->>VM: Result(...)
alt 성공
VM-->>S: LoadUserOnBoardingSuccess(요약, userName)
note right of S: Existed 요약 표시
else 실패
VM-->>S: LoadUserOnBoardingFailure(msg)
S->>S: ShowToast(msg)
S-->>U: 이전 화면으로 이동
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt (1)
16-24: 0으로 나누기 방지 및 progress 범위 보장progress = currentStep / totalStep 계산에서 totalStep이 0일 경우 NaN이 되어 하위 로직(showProgress/hideToolbar) 판정이 흐려질 수 있습니다. 또한 음수/초과 값 방지를 위해 coerceIn으로 클램프를 권장합니다.
- ) : OnBoardingState(progress = currentStep.toFloat() / totalStep.toFloat()) + ) : OnBoardingState( + progress = when { + totalStep <= 0 -> 0f + else -> (currentStep.toFloat() / totalStep.toFloat()).coerceIn(0f, 1f) + }, + )
🧹 Nitpick comments (24)
domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt (1)
15-16: getUserOnBoarding 반환 모델의 타입 안정성 강화 제안현재 Result<List<Pair<String, List>>> 형태는 키(String)에 암묵적으로 의존해 가독성과 안정성이 낮습니다. 도메인 쪽에 식별자 타입(예: sealed interface/enum)이나 typealias, 혹은 작은 값 객체(data class OnBoardingSelection(id: OnBoardingId, values: List))를 도입해 계약을 명시적으로 고정해 주세요. 또한 키 집합(TimeSlot, EmotionType(s), TargetOutingFrequency)과 값의 의미(각 값의 카디널리티)도 KDoc로 문서화하면 유지보수에 유리합니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt (1)
8-9: 토스트 메시지 전달에 UiText/리소스 ID 추상화 고려String을 직접 싣는 대신 UiText(리소스/포맷 지원)나 @stringres 기반의 변형을 사용하면 i18n, 테스트, 카피 변경에 유리합니다. 예: ShowToastRes(@stringres id: Int, vararg args: Any) 또는 ShowToast(val message: UiText).
data/src/main/java/com/threegap/bitnagil/data/onboarding/datasource/OnBoardingDataSource.kt (1)
9-15: Result 노출 방식의 일관성 정리 제안동일 인터페이스 내에서 반환 타입이 혼재합니다.
- 네트워크 호출로 보이는 메서드: getOnBoardingRecommendRoutines(...), getUserOnBoarding() → Result<...>
- 그 외: getOnBoardingList(), getOnBoardingAbstract(...) → 비-Result
의도(로컬/원천 데이터 차이)가 명확하다면 주석으로 이유를 남기거나, 가능하면 네트워크 경로는 전부 Result로 통일하는 편이 사용/테스트/에러 처리 측면에서 안정적입니다.
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt (2)
48-56: 프리뷰 유지 보수성 향상 제안프리뷰에서도 기본값을 활용하면 호출부가 단순해집니다. 위 제안대로 기본값을 추가하면 프리뷰 인자에서 showProgress를 생략할 수 있습니다.
23-24: showProgress에 기본값(true) 추가하여 기존 호출부 호환성 유지showProgress 파라미터에 기본값을 부여하지 않으면, 기존 호출부(예: OnBoardingScreen.kt:82)에서 컴파일 에러가 발생합니다.
다음과 같이 기본값을 설정해주세요:파일: core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt
fun BitnagilProgressTopBar( progress: Float, onBackClick: () -> Unit, - showProgress: Boolean, + showProgress: Boolean = true, ) { … }• 호출부 예시
- presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt:82 (showProgress 인자 미전달)
• 기본값 추가만으로 모든 기존 호출부 수정 없이 빌드/실행 가능domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetUserOnBoardingUseCase.kt (1)
9-11: 도메인 경계의 타입 안정성 고려(선택)Result<List<Pair<String, List>>>는 호출 측에서 키/값의 의미를 매번 암묵적으로 알아야 해 사용성/가독성이 떨어질 수 있습니다. 도메인 VO로 감싸거나, 명시적 필드명을 가진 데이터 클래스로 매핑해 주면 의도가 선명해집니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/common/ninepatch/NinePatchBackground.kt (1)
3-30: Compose 관용구로 리팩터링 및 구성 변경 대응성 개선현재 @composable 확장 함수에서 remember(drawableResId)만 키로 사용합니다. 다크모드/locale 등의 구성 변경 시 리소스 재적재가 누락될 수 있어 LocalConfiguration을 키에 포함하는 것을 권장합니다. 또한 Modifier 확장은 composed 블록을 사용하는 패턴이 일반적이며, 이름에서 Node를 제거하면 역할이 더 분명합니다.
아래와 같이 리팩터링을 제안합니다.
-package com.threegap.bitnagil.presentation.common.ninepatch - -import android.graphics.drawable.NinePatchDrawable -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.graphics.nativeCanvas -import androidx.compose.ui.platform.LocalContext -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.updateBounds - -@Composable -fun Modifier.ninePatchBackgroundNode(drawableResId: Int): Modifier { - val context = LocalContext.current - val ninePatchDrawable = remember(drawableResId) { - ContextCompat.getDrawable(context, drawableResId) as? NinePatchDrawable - } - - return if (ninePatchDrawable != null) { - this.then( - Modifier.drawBehind { - ninePatchDrawable.updateBounds(0, 0, size.width.toInt(), size.height.toInt()) - ninePatchDrawable.draw(drawContext.canvas.nativeCanvas) - }, - ) - } else { - this - } -} +package com.threegap.bitnagil.presentation.common.ninepatch + +import android.graphics.drawable.NinePatchDrawable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.updateBounds + +fun Modifier.ninePatchBackground(drawableResId: Int): Modifier = composed { + val context = LocalContext.current + val configuration = LocalConfiguration.current + val ninePatchDrawable = remember(drawableResId, configuration) { + ContextCompat.getDrawable(context, drawableResId) as? NinePatchDrawable + } + if (ninePatchDrawable == null) { + this + } else { + this.then( + Modifier.drawBehind { + ninePatchDrawable.updateBounds(0, 0, size.width.toInt(), size.height.toInt()) + ninePatchDrawable.draw(drawContext.canvas.nativeCanvas) + }, + ) + } +}기존 호출부 호환을 위해 다음과 같은 디프리케이션 래퍼를 추가하는 것도 고려해주세요(파일 하단 등 별도 위치에 추가).
@Deprecated("함수명을 ninePatchBackground로 변경했습니다.", ReplaceWith("this.ninePatchBackground(drawableResId)")) fun Modifier.ninePatchBackgroundNode(drawableResId: Int): Modifier = this.ninePatchBackground(drawableResId)data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt (1)
23-24: 네이밍 일관성: getOnBoarding → getUserOnBoarding 검토도메인/레포지토리 레이어가
getUserOnBoarding네이밍을 사용한다면, 서비스도 동일한 네이밍으로 맞추면 검색성과 일관성이 좋아집니다. 필수는 아니므로 다음 리팩터 주기에 고려해주세요.presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt (1)
17-17: 에러 전달을 String 대신 타입 안전한 에러 모델로 교체 제안
LoadUserOnBoardingFailure(val message: String)는 문자열 기반이라 후속 처리(로깅/분기/로컬라이징)가 어려워질 수 있습니다. 최소한 에러 코드/원인 구분이 가능한 도메인 에러 타입으로 교체를 고려해 주세요.예:
- sealed interface OnBoardingError { object Network; object Unauthorized; data class Unknown(val cause: Throwable) }
- Failure Intent는
val error: OnBoardingError로 보유presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingPageInfo.kt (2)
13-18: ExistedOnBoardingAbstract와 Abstract 구조 중복 — 단일 타입으로 통합 고려두 타입이 모두
(prefix: String, abstractTexts: List<List<OnBoardingAbstractTextItem>>)동일 구조를 갖습니다. 의미적 차이가 없다면Abstract로 일원화하고 “출처(신규/기존)”는 상위 상태나 Intent에서 구분하면 중복 UI/매핑 코드를 줄일 수 있습니다.
13-18: Compose 안정성 표기 개선: @stable 필드 제거 + 클래스에 @immutable 부여 제안
@Stable은 타입/클래스에 부여할 때 효과적이며, 프로퍼티 수준 주석은 의미가 없습니다. 데이터 클래스 자체를@Immutable로 표기하고, 프로퍼티의@Stable은 제거하는 편이 Compose 재구성 힌트에 더 적합합니다.다음 diff를 적용해 주세요:
@Parcelize -data class ExistedOnBoardingAbstract( +@Immutable +data class ExistedOnBoardingAbstract( val prefix: String, - @Stable val abstractTexts: List<List<OnBoardingAbstractTextItem>>, + val abstractTexts: List<List<OnBoardingAbstractTextItem>>, ) : OnBoardingPageInfo()@Parcelize -data class Abstract( +@Immutable +data class Abstract( val prefix: String, - @Stable val abstractTexts: List<List<OnBoardingAbstractTextItem>>, + val abstractTexts: List<List<OnBoardingAbstractTextItem>>, ) : OnBoardingPageInfo()추가로 파일 상단에 다음 import를 추가하세요(선택):
import androidx.compose.runtime.ImmutableAlso applies to: 68-72
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt (1)
44-47: 접근성(스크린리더) 개선: ProgressBar semantics 추가 제안현재 ProgressBar의 진행도를 스크린리더가 읽을 수 없습니다.
semantics { progressBarRangeInfo = ... }를 추가하면 접근성이 개선됩니다.적용 diff:
Canvas( - modifier = modifier - .fillMaxWidth() - .height(height), + modifier = modifier + .fillMaxWidth() + .height(height) + .semantics { + progressBarRangeInfo = ProgressBarRangeInfo(animatedProgress, 0f..1f) + }, ) {추가 import:
import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.progressBarRangeInfopresentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt (3)
33-37: 긴 사용자 이름 대응: 말줄임 처리 권장헤더 문구가 사용자 이름에 따라 1줄을 초과할 수 있습니다.
maxLines/overflow로 안전장치 추가를 권장합니다.Text( text = "포모는 ${userName}님을 알고 싶어요!", style = BitnagilTheme.typography.title1Bold, color = BitnagilTheme.colors.coolGray10, + maxLines = 1, + overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, )
33-37: 하드코딩 문자열을 stringResource로 이전(로컬라이징/테스팅/접근성 향상)문자열 하드코딩은 다국어/카피 수정/스냅샷 테스트에 취약합니다. 모두 string 리소스로 이전해 주세요.
적용 diff 예시:
- Text( - text = "포모는 ${userName}님을 알고 싶어요!", + Text( + text = stringResource(R.string.onboarding_intro_title, userName), style = BitnagilTheme.typography.title1Bold, color = BitnagilTheme.colors.coolGray10, ... ) - Text( + Text( modifier = Modifier .ninePatchBackgroundNode(R.drawable.img_texture_rectangle_1) .padding(horizontal = 27.dp, vertical = 20.dp), - text = "어떤 시간대를 더 잘보내고 싶은지", + text = stringResource(R.string.onboarding_intro_chip_time), ... - Text( + Text( modifier = Modifier .align(Alignment.End) .offset(y = (-10).dp) .ninePatchBackgroundNode(R.drawable.img_texture_rectangle_2) .padding(horizontal = 27.dp, vertical = 20.dp), - text = "요즘 어떤 회복이 필요한지", + text = stringResource(R.string.onboarding_intro_chip_recovery), ... - Text( + Text( modifier = Modifier .offset(y = (-18).dp) .padding(start = 22.dp) .ninePatchBackgroundNode(R.drawable.img_texture_rectangle_3) .padding(horizontal = 27.dp, vertical = 20.dp), - text = "얼마나 바깥 바람을 쐬고 싶은지", + text = stringResource(R.string.onboarding_intro_chip_outdoor), ... - BitnagilTextButton( - text = "포모에게 알려주기", + BitnagilTextButton( + text = stringResource(R.string.onboarding_intro_button_next), onClick = onClickNextButton, ... )추가 import:
import androidx.compose.ui.res.stringResource예시 strings.xml (모듈 리소스에 추가):
<resources> <string name="onboarding_intro_title">포모는 %1$s님을 알고 싶어요!</string> <string name="onboarding_intro_chip_time">어떤 시간대를 더 잘보내고 싶은지</string> <string name="onboarding_intro_chip_recovery">요즘 어떤 회복이 필요한지</string> <string name="onboarding_intro_chip_outdoor">얼마나 바깥 바람을 쐬고 싶은지</string> <string name="onboarding_intro_button_next">포모에게 알려주기</string> <string name="onboarding_intro_cd_pomo_memo">포모 메모 일러스트</string> </resources>참고: 이미지가 장식용이 아니고 의미가 있다면
contentDescription = stringResource(R.string.onboarding_intro_cd_pomo_memo)로 변경해 주세요.Also applies to: 44-51, 52-61, 62-71, 86-91
86-91: 불필요한 enabled = true 제거기본값이 true라서 명시가 불필요합니다. 간결성을 위해 제거를 제안합니다.
BitnagilTextButton( text = "포모에게 알려주기", onClick = onClickNextButton, - enabled = true, modifier = Modifier.fillMaxWidth(), )presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (1)
102-108: 문자열 하드코딩 대신 string 리소스 사용 권장"이제 포모가 당신에게\n꼭 맞는 루틴을 찾아줄거에요."와 같은 표시 문자열은 다국어/접근성/일관성을 위해 string 리소스로 관리하는 것을 권장합니다.
예시:
- title = "이제 포모가 당신에게\n꼭 맞는 루틴을 찾아줄거에요.", + title = stringResource(R.string.onboarding_abstract_title),리소스 추가가 필요합니다:
// res/values/strings.xml <string name="onboarding_abstract_title">이제 포모가 당신에게\n꼭 맞는 루틴을 찾아줄거에요.</string>presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (4)
79-89: fold + 비지역 반환 사용 패턴 간결화 제안
fold내부에서return@launch를 사용하는 비지역 반환은 읽기 난이도를 올릴 수 있습니다.getOrElse를 사용하면 의도를 더 명확히 표현할 수 있습니다.- val userOnBoarding = getUserOnBoardingUseCase().fold( - onSuccess = { it }, - onFailure = { - sendIntent(OnBoardingIntent.LoadUserOnBoardingFailure(message = it.message ?: "에러가 발생했습니다. 잠시 후 시도해주세요.")) - return@launch - }, - ) + val userOnBoarding = getUserOnBoardingUseCase().getOrElse { + sendIntent( + OnBoardingIntent.LoadUserOnBoardingFailure( + message = it.message ?: "에러가 발생했습니다. 잠시 후 시도해주세요." + ) + ) + return@launch + }
338-369: fire-and-forget에 async 대신 launch 사용 권장, 실패 처리 추가 제안
async는 결과 합류가 필요한 경우에 적합합니다. 여기서는 단순 실행/취소만 필요하므로launch가 더 자연스럽습니다. 또한 실패 케이스에서 사용자 피드백(토스트 등)을 주는 것이 UX에 도움이 됩니다.- fun loadRecommendRoutines() { - loadRecommendRoutinesJob?.cancel() - loadRecommendRoutinesJob = viewModelScope.async { + fun loadRecommendRoutines() { + loadRecommendRoutinesJob?.cancel() + loadRecommendRoutinesJob = viewModelScope.launch { val selectedItems = selectOnBoardingPageInfos .map { onBoardingPage -> val id = onBoardingPage.id val selectedItemIds = onBoardingPage.items.filter { onBoardingItem -> onBoardingItem.selectedIndex != null }.sortedBy { onBoardingItem -> onBoardingItem.selectedIndex }.map { onBoardingItem -> onBoardingItem.id } Pair(id, selectedItemIds) } getRecommendOnBoardingRoutinesUseCase(selectedItems).fold( onSuccess = { recommendRoutines -> if (isActive) { sendIntent( intent = OnBoardingIntent.LoadRecommendRoutinesSuccess( routines = recommendRoutines.map { OnBoardingItem.fromOnBoardingRecommendRoutine(it) }, ), ) } }, - onFailure = { - }, + onFailure = { e -> + if (isActive) { + sendSideEffect(OnBoardingSideEffect.ShowToast(e.message ?: "추천 루틴을 불러오지 못했어요.")) + } + }, ) } }
340-351: 선택 항목 매핑 로직 중복 — 헬퍼 재사용으로 일관성 확보
getSelectedOnBoardingItemIdsWithId()와 거의 동일한 로직이 여기에도 존재합니다(단, 정렬 여부만 다름). 헬퍼를 재사용하거나 옵션 파라미터로 정렬을 제어하도록 통합하면 유지보수가 쉬워집니다.예시(정렬 옵션 추가):
private fun getSelectedOnBoardingItemIdsWithId( onboardingPageInfos: List<OnBoardingPageInfo.SelectOnBoarding>, sortBySelectedIndex: Boolean = false, ): List<Pair<String, List<String>>> { return onboardingPageInfos.map { onBoardingPage -> val id = onBoardingPage.id val items = onBoardingPage.items.filter { it.selectedIndex != null } val ordered = if (sortBySelectedIndex) items.sortedBy { it.selectedIndex } else items id to ordered.map { it.id } } }사용부:
- val selectedItems = selectOnBoardingPageInfos - .map { ...정렬 및 매핑... } + val selectedItems = getSelectedOnBoardingItemIdsWithId(selectOnBoardingPageInfos, sortBySelectedIndex = true)
151-162: totalStep 계산 주석 또는 상수화 제안
totalStep = selectOnBoardingPageInfos.size + 2에서+2는 (Abstract, Recommend) 추가 페이지를 의미하는 듯합니다. 매직 넘버 대신 상수/주석으로 의도를 드러내면 가독성이 좋아집니다.- totalStep = selectOnBoardingPageInfos.size + 2, + // +2: Abstract, Recommend 단계 포함 + totalStep = selectOnBoardingPageInfos.size + 2,presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt (4)
70-79: 상단 일러스트 배치 변경 LGTM상단 일러스트를 전체 폭으로 배치하고 zIndex로 레이어링한 구성은 디자인 의도에 부합해 보입니다. 다만 접근성을 위해
contentDescription을 적절히 제공하는 것을 권장합니다(스크린리더 사용자를 고려).- Image( - painter = painterResource(R.drawable.img_pomo_flower), - contentDescription = null, + Image( + painter = painterResource(R.drawable.img_pomo_flower), + contentDescription = null, // 혹은 stringResource(R.string.cd_onboarding_flower) 등으로 정의리소스 준비 시에만 실제 문자열을 연결해 주세요.
101-109: 버튼 텍스트 하드코딩 → string 리소스 권장"다음"은 공용 버튼 텍스트이므로 string 리소스 사용이 바람직합니다.
- BitnagilTextButton( - text = "다음", + BitnagilTextButton( + text = stringResource(R.string.common_next),
140-156: 아이콘 Image 접근성 및 패딩 체이닝 포매팅아이콘의
contentDescription을 명시하거나contentDescription = null주석으로 장식용임을 분명히 해주세요. 또한modifier체이닝은 가독성을 위해 줄바꿈 정렬을 권장합니다.- Image( - painter = painterResource(iconResourceId), - modifier = Modifier.padding(top = 8.dp).size(24.dp), - contentDescription = null, - ) + Image( + painter = painterResource(iconResourceId), + modifier = Modifier + .padding(top = 8.dp) + .size(24.dp), + contentDescription = null, // decorative + )
165-171: lineHeight 계산 간소화 및 중복 지정 제거
Dp -> Sp변환 로직은 과합니다. lineHeight는 Sp로 직접 지정하고,style.copy(lineHeight = ...)와Text파라미터lineHeight를 동시에 설정하는 중복도 제거해주세요.- var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) } - val density = LocalDensity.current - val lineHeight = with(density) { - val extraSpacingInDp = 39.dp - return@with (extraSpacingInDp.value).dp.toSp() - } + var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) } + val lineHeight = 39.sp @@ - Text( - text = text, - color = BitnagilTheme.colors.coolGray30, - style = BitnagilTheme.typography.body2Regular.copy( - lineHeight = lineHeight, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - ), - lineHeight = lineHeight, + Text( + text = text, + color = BitnagilTheme.colors.coolGray30, + style = BitnagilTheme.typography.body2Regular.copy( + lineHeight = lineHeight, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ),추가로 필요한 import:
import androidx.compose.ui.unit.sp[Note] 그리기 로직(
drawBehind)은getLineBottom(i)사용으로 라인 하단에 정확히 맞춰 긋고 있습니다. 폰트/라인 높이 변경 시에도 안정적으로 동작합니다.Also applies to: 172-204
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (30)
core/designsystem/src/main/res/drawable-hdpi/img_pomo_flower.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-hdpi/img_pomo_memo.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_1.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_2.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_3.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_4.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-mdpi/img_pomo_flower.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-mdpi/img_pomo_memo.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_1.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_2.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_3.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_4.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xhdpi/img_pomo_flower.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xhdpi/img_pomo_memo.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_1.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_2.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_3.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_4.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxhdpi/img_pomo_flower.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxhdpi/img_pomo_memo.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_1.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_2.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_3.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_4.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_flower.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_memo.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_1.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_2.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_3.9.pngis excluded by!**/*.pngcore/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_4.9.pngis excluded by!**/*.png
📒 Files selected for processing (19)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt(2 hunks)core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt(3 hunks)data/src/main/java/com/threegap/bitnagil/data/onboarding/datasource/OnBoardingDataSource.kt(1 hunks)data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt(2 hunks)data/src/main/java/com/threegap/bitnagil/data/onboarding/model/request/GetOnBoardingRecommendRoutinesRequest.kt(1 hunks)data/src/main/java/com/threegap/bitnagil/data/onboarding/model/response/GetUserOnBoardingResponse.kt(1 hunks)data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt(2 hunks)data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt(1 hunks)domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt(1 hunks)domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetUserOnBoardingUseCase.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/common/ninepatch/NinePatchBackground.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt(6 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt(10 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingPageInfo.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T13:32:26.263Z
Learnt from: l5x5l
PR: YAPP-Github/Bitnagil-Android#41
File: presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageIntent.kt:6-6
Timestamp: 2025-07-23T13:32:26.263Z
Learning: In the Bitnagil Android project's MVI architecture, Intent classes like `LoadMyPageSuccess` are named to represent successful API response results that carry loaded data, not just user actions. This naming convention is used for future API integration where the intent will be triggered when my page data loading succeeds from the server.
Applied to files:
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt
🧬 Code Graph Analysis (6)
data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt (1)
data/src/main/java/com/threegap/bitnagil/data/common/SafeApiCall.kt (1)
safeApiCall(10-25)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt (1)
BitnagilProgressBar(30-69)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt (2)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)
BitnagilTextButton(34-85)core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/Theme.kt (1)
BitnagilTheme(26-41)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (2)
sendIntent(30-37)sendSideEffect(23-23)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)
BitnagilTextButton(34-85)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (3)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt (1)
BitnagilProgressTopBar(18-46)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt (1)
OnBoardingIntroTemplate(23-93)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt (1)
OnBoardingAbstractTemplate(43-110)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (20)
data/src/main/java/com/threegap/bitnagil/data/onboarding/model/request/GetOnBoardingRecommendRoutinesRequest.kt (1)
11-16: GetOnBoardingRecommendRoutinesRequest 호출부 업데이트 완료
리포지토리 구현부에서 emotionType이 모두 List으로 올바르게 처리되어 있습니다. 빠진 호출부가 없습니다.
- data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt
– 43행:emotionType = emotionType ?: listOf()프로퍼티명을 복수형으로 맞추고 싶다면 아래와 같이 선택적으로 변경할 수 있습니다(직렬화 키는 그대로 유지).
- @SerialName("emotionType") - val emotionType: List<String>, + @SerialName("emotionType") + val emotionTypes: List<String>,data/src/main/java/com/threegap/bitnagil/data/onboarding/model/response/GetUserOnBoardingResponse.kt (1)
6-14: 새로운 응답 DTO 추가: 구성 적절합니다timeSlot/String, emotionTypes/List, targetOutingFrequency/String의 맵핑이 명확하고, @Serializable/@SerialName 사용도 올바릅니다. 상위 safeApiCall과의 궁합도 문제없어 보입니다.
data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt (3)
14-15: 필요한 DTO import 추가: OK
61-66: safeApiCall로 감싼 위임 구현: OKonBoardingService.getOnBoarding()를 안전 래퍼로 감싼 구현이 일관적이며 Result 계약과도 부합합니다. EMPTY_DATA 처리도 상위 safeApiCall에서 커버됩니다.
61-66: 서비스/리포지토리 체인 및 도메인 매핑 확인 완료
DataSource(getUserOnBoarding()), Service(getOnBoarding()), RepositoryImpl(getUserOnBoarding(): Result<List<Pair<String, List<String>>>>)가 모두 연결되어 있으며, RepositoryImpl에서List<Pair<String, List<String>>>형태로 도메인 요구사항에 맞춰 매핑되고 있음을 확인했습니다.core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt (1)
39-44: 조건부 렌더링은 적절합니다showProgress로 내부 ProgressBar를 토글하는 접근은 단순하고 명확합니다. BitnagilProgressBar 내부에서 progress.coerceIn(0f, 1f)를 이미 사용 중이라 안전합니다.
domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetUserOnBoardingUseCase.kt (1)
6-12: 간결하고 일관된 유즈케이스 구현Repository 위임 형태로 깔끔합니다. operator invoke와 DI 사용도 적절합니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt (1)
25-27: 파생 프로퍼티 로직은 명확합니다showProgress/hideToolbar는 UI 요구사항을 잘 반영합니다. 위 progress 안정화와 함께 쓰면 엣지 케이스에서도 의도대로 동작할 것입니다.
data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt (1)
13-24: v2 온보딩 API 마이그레이션 및 GET 추가 확인 완료
/api/v1/onboardings관련 잔여 경로가 존재하지 않습니다.postOnBoarding,postOnBoardingRoutines,getOnBoarding메서드는 모두OnBoardingDataSourceImpl에서 정상 호출되고 있습니다.- Retrofit 어노테이션과 함수 시그니처가 일관적이고 적절하게 적용되었습니다.
서버 응답 스키마(
BaseResponse<GetUserOnBoardingResponse>)와 로컬 모델 필드 매핑이 실제 API 응답과 일치하는지 한 번 더 확인 부탁드립니다.presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt (1)
16-18: Intent 추가 네이밍/용도 모두 프로젝트 컨벤션에 부합합니다
LoadUserOnBoardingSuccess/Failure,LoadIntroSuccess는 기존 학습된 컨벤션(성공 결과를 담는 Intent)과 정확히 맞습니다. ViewModel 플로우와도 잘 연결됩니다.core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt (2)
61-61: 그라데이션 → 단색 전환 깔끔합니다. API 축소도 명확합니다
progressColor+backgroundColor2요소로 단순화되어 사용성이 좋아졌습니다.- 기본 색상도 디자인 토큰과 일관적입니다.
Also applies to: 73-81
72-81: API 변경 영향 검증 완료
정적 점검 스크립트를 실행한 결과,gradientStartColor/gradientEndColor사용 흔적이 전혀 없으며,BitnagilProgressBarColor호출부도 내부default()팩토리 메서드 외에는 발견되지 않았습니다. 변경된 API가 코드베이스에 미치는 영향은 없습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt (1)
76-82: 이미지 contentDescription null 사용 적절데코레이션 용도라면
contentDescription = null이 맞습니다. 추후 접근성 요구사항에 따라 설명이 필요할 때는 stringResource로 대체하면 됩니다.data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt (2)
37-37: emotionType 다중 선택 적용 LGTMemotionType을
List<String>로 받아 그대로 전달하고, 기본값을 빈 리스트로 처리한 흐름이 명확합니다. 서버 스키마(v2) 변경과 일관되며, 널/결측값도 안전하게 처리됩니다.Also applies to: 43-43
67-75: 사용자 온보딩 매핑 적절하나, RealOutingFrequency 제외 의도 확인 필요
getUserOnBoarding()에서TimeSlot,EmotionType,TargetOutingFrequency만 매핑하고 있습니다. v2에서RealOutingFrequency가 필요 없어진 게 의도라면 OK입니다. 아니라면 누락으로 인해 요약/추천 로직에 영향이 있을 수 있으니 확인 바랍니다.필요 시 다음과 같이
RealOutingFrequency도 포함하도록 확장할 수 있습니다(예시):return onBoardingDataSource.getUserOnBoarding().map { listOf( OnBoardingDto.TimeSlot.id to listOf(it.timeSlot), OnBoardingDto.EmotionType.id to it.emotionTypes, OnBoardingDto.TargetOutingFrequency.id to listOf(it.targetOutingFrequency), + OnBoardingDto.RealOutingFrequency.id to listOfNotNull(it.realOutingFrequency), ) }presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (4)
43-45: 사이드이펙트 기반 토스트 처리 LGTM
OnBoardingSideEffect.ShowToast를 UI 레이어에서 안전하게 처리하고 있습니다. 비즈니스 로직과 UI 사이드이펙트 분리가 잘 되어 있습니다.
78-78: TopBar 노출 조건 분기와 Progress 제어 좋습니다툴바를
hideToolbar로 제어하고,showProgress로 프로그레스바 표시를 별도로 결정하는 구조가 명확합니다. 배경색을 white로 바꾼 부분도 리디자인 의도에 부합해 보입니다.Also applies to: 81-88
93-98: 인트로 단계 연결 흐름 명확화 LGTMIntro 화면에서 바로 온보딩 항목 로딩으로 넘어가는 트리거가 직관적입니다. 사용자명 전달도 일관됩니다.
142-152: 기존 온보딩 요약 단계 처리 흐름 설계 적절이전에 설정된 목표가 있을 때 요약을 보여주고, 다음 액션을 온보딩 항목 로딩으로 유도하는 UX가 명확합니다. next 버튼 활성화 고정(true)도 의도에 맞습니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt (1)
45-52: 템플릿 API 변경 방향성 적절
onInit제거,userName전달,onClickNextButton및nextButtonEnable명확화로 호출측 책임이 분리되어 사용성이 좋아졌습니다.
...c/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt
Show resolved
Hide resolved
wjdrjs00
left a comment
There was a problem hiding this comment.
고생하셨습니다~!
[1] 현재 온보딩단계에서는 문제가 없는데, 내 목표 재설정의 경우 마지막에 제공되는 추천루틴이 선택안되는 문제가 발생해서, 해결 부탁드립니다요!
.../com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt
Show resolved
Hide resolved
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt
Show resolved
Hide resolved
반영 완! -> 5b71f6f |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (1)
118-123: 추천 루틴 단계 스킵 조건 로직 수정 제안 (필드명 보정)
OnBoardingSetType모델에는selectionRequiredAtRecommendStep가 아닌mustSelectRecommendRoutine필드가 정의되어 있습니다. 따라서 리뷰 제안에 사용된 프로퍼티명을 다음과 같이 변경해주세요.• 수정 전
onClickSkip = if (state.onBoardingSetType.canSkip && !state.onBoardingSetType.selectionRequiredAtRecommendStep ) onClickSkip else null,• 수정 후
- onClickSkip = - if (state.onBoardingSetType.canSkip && - !state.onBoardingSetType.selectionRequiredAtRecommendStep - ) onClickSkip else null, + onClickSkip = + if (state.onBoardingSetType.canSkip && + !state.onBoardingSetType.mustSelectRecommendRoutine + ) onClickSkip else null,이렇게 변경하면 “선택 필수(=mustSelectRecommendRoutine=true)”인 경우 스킵 버튼이 노출되지 않아 UX 정책과 일치하게 됩니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
111-120: 온보딩 항목 로드 실패/빈 목록 처리 보강 필요
getOnBoardingsUseCase()실패 시 UI가 멈출 수 있고,- 빈 목록일 때
.first()접근으로 NPE/Index 오류 리스크가 있습니다.토스트 노출과 빈 목록 가드 추가를 제안합니다.
fun loadOnBoardingItems() { viewModelScope.launch { - val onBoardings = getOnBoardingsUseCase() - val onBoardingPages = onBoardings.map { onBoarding -> - OnBoardingPageInfo.SelectOnBoarding.fromOnBoarding(onBoarding = onBoarding) - } - - sendIntent(intent = OnBoardingIntent.LoadOnBoardingSuccess(onBoardingPageInfos = onBoardingPages)) + runCatching { getOnBoardingsUseCase() } + .map { onBoardings -> + onBoardings.map { onBoarding -> + OnBoardingPageInfo.SelectOnBoarding.fromOnBoarding(onBoarding) + } + }.onSuccess { pages -> + sendIntent(OnBoardingIntent.LoadOnBoardingSuccess(onBoardingPageInfos = pages)) + }.onFailure { + sendSideEffect(OnBoardingSideEffect.ShowToast("온보딩 항목을 불러오지 못했습니다. 잠시 후 다시 시도해주세요.")) + } } } @@ is OnBoardingIntent.LoadOnBoardingSuccess -> { val currentState = state if (currentState !is OnBoardingState.Idle) return null - selectOnBoardingPageInfos.clear() - selectOnBoardingPageInfos.addAll(intent.onBoardingPageInfos) + selectOnBoardingPageInfos.clear() + selectOnBoardingPageInfos.addAll(intent.onBoardingPageInfos) - return currentState.copy( - nextButtonEnable = false, - currentOnBoardingPageInfo = intent.onBoardingPageInfos.first(), - totalStep = selectOnBoardingPageInfos.size + 2, - currentStep = 1, - ) + if (intent.onBoardingPageInfos.isEmpty()) { + sendSideEffect(OnBoardingSideEffect.ShowToast("온보딩 항목이 없습니다. 잠시 후 다시 시도해주세요.")) + return currentState // 상태 유지 + } + return currentState.copy( + nextButtonEnable = false, + currentOnBoardingPageInfo = intent.onBoardingPageInfos.first(), + totalStep = selectOnBoardingPageInfos.size + 2, + currentStep = 1, + ) }Also applies to: 151-164
🧹 Nitpick comments (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (1)
102-102: 사용자 노출 문구 맞춤법 수정 제안"찾아줄거에요." → "찾아줄 거예요."가 맞춤법상 자연스럽습니다.
- title = "이제 포모가 당신에게\n꼭 맞는 루틴을 찾아줄거에요.", + title = "이제 포모가 당신에게\n꼭 맞는 루틴을 찾아줄 거예요.",presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (3)
239-241: 선택 필수 플래그 사용처 정렬 필요여기서도
!mustSelectRecommendRoutine로 뒤집어 사용 중입니다. 앞서 제안한 리네이밍(selectionRequiredAtRecommendStep)을 적용하면 조건이 자연스럽습니다.- nextButtonEnable = !currentState.onBoardingSetType.mustSelectRecommendRoutine, + nextButtonEnable = !currentState.onBoardingSetType.selectionRequiredAtRecommendStep,
337-369: 추천 루틴 로드 실패 처리 누락 + async → launch 권장
- 실패 시 아무 피드백이 없어 UX 공백이 발생합니다.
- 반환값을 사용하지 않으므로
async대신launch가 표현적으로 맞습니다(둘 다Job이므로 취소 로직 동일).- fun loadRecommendRoutines() { - loadRecommendRoutinesJob?.cancel() - loadRecommendRoutinesJob = viewModelScope.async { + fun loadRecommendRoutines() { + loadRecommendRoutinesJob?.cancel() + loadRecommendRoutinesJob = viewModelScope.launch { val selectedItems = selectOnBoardingPageInfos @@ - getRecommendOnBoardingRoutinesUseCase(selectedItems).fold( + getRecommendOnBoardingRoutinesUseCase(selectedItems).fold( onSuccess = { recommendRoutines -> if (isActive) { sendIntent( intent = OnBoardingIntent.LoadRecommendRoutinesSuccess( routines = recommendRoutines.map { OnBoardingItem.fromOnBoardingRecommendRoutine(it) }, ), ) } }, - onFailure = { - }, + onFailure = { + if (isActive) { + sendSideEffect(OnBoardingSideEffect.ShowToast("추천 루틴을 불러오지 못했습니다. 잠시 후 다시 시도해주세요.")) + } + }, ) } }추가로, 선택된 항목 가공 로직이
getSelectedOnBoardingItemIdsWithId(...)와 중복됩니다. 정렬 필요 시 파라미터(sortBySelectedIndex)로 제어하도록 헬퍼를 확장하는 방식도 고려하세요.
395-401: 추천 루틴 등록 실패 처리 누락등록 실패 시에도 사용자 피드백이 필요합니다. 토스트를 추가하세요.
- registerRecommendOnBoardingRoutinesUseCase(selectedRecommendRoutineIds = selectedRoutineIds).fold( + registerRecommendOnBoardingRoutinesUseCase(selectedRecommendRoutineIds = selectedRoutineIds).fold( onSuccess = { _ -> sendIntent(intent = OnBoardingIntent.NavigateToHome) }, - onFailure = { - }, + onFailure = { + sendSideEffect(OnBoardingSideEffect.ShowToast("추천 루틴 등록에 실패했습니다. 잠시 후 다시 시도해주세요.")) + }, )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt(7 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt(11 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingSetType.kt(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (3)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt (1)
BitnagilProgressTopBar(18-46)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt (1)
OnBoardingIntroTemplate(23-93)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt (1)
OnBoardingAbstractTemplate(43-110)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (2)
sendIntent(30-37)sendSideEffect(23-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (4)
43-45: 사이드이펙트 → 글로벌 토스트 연결 적절
OnBoardingSideEffect.ShowToast를GlobalBitnagilToast.showWarning으로 매핑한 것은 일관되고 간결합니다. 별도 우려 없습니다.
81-88: 툴바 노출 제어 + 진행바 표시 제어 LGTM
hideToolbar로 노출 제어,showProgress로 진행바 가시성 제어를 분리한 구성은 재사용성과 테스트 용이성을 높입니다.
138-147: 기존 온보딩 요약 → 선택 단계 진입 흐름 연결 깔끔Existed 요약 화면에서
onClickLoadOnBoarding으로 선택 단계로 진입시키는 구성은 명확합니다. 다만 뒤로가기 동작은 뷰모델 로직(아래 코멘트) 보완이 필요합니다.
172-173: 프리뷰 업데이트 적절프리뷰에
userName과 신규 콜백을 반영한 점 좋습니다. 디자인 QA에 도움이 됩니다.Also applies to: 175-175
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
55-69: 초기 분기(NEW/RESET)로 로딩 경로 분리 깔끔초기 진입점에서 Intro/기존 온보딩 로드로 분기하는 설계는 상태 전이를 단순화합니다. 좋습니다.
...ation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingSetType.kt
Show resolved
Hide resolved
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
204-218: RESET 경로 뒤로가기 버그 수정 반영 — 잘 처리되었습니다
1단계에서 RESET일 때 Intro가 아닌 기존 요약으로 복귀하도록 분기 추가된 점 확인했습니다. 지난 코멘트 제안이 반영되었습니다.
🧹 Nitpick comments (7)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (7)
71-77: 닉네임 기본값 하드코딩('-') 대신 리소스/도메인 디폴트 사용 권장
뷰모델에서 문자열 하드코딩은 국제화/테스트 측면에서 불리합니다. 빈 문자열을 주고 UI에서 리소스로 대체하거나, 도메인 레이어에서 안전한 디폴트를 제공하는 쪽을 권장합니다.- val userName = fetchUserProfileUseCase().getOrNull()?.nickname ?: "-" + val userName = fetchUserProfileUseCase().getOrNull()?.nickname.orEmpty()
92-99: 중복된 Abstract 매핑 로직을 헬퍼로 추출해 중복 제거
아래 동일 매핑이 본 파일에 2회 등장합니다(여기와 Lines 306-311). 유지보수성을 위해 헬퍼로 추출을 권장합니다.- val abstractPagePrefixText = onBoardingAbstract.prefix - val abstractTexts = onBoardingAbstract.abstractTexts.map { onBoardingAbstractText -> - onBoardingAbstractText.textItems.map { onBoardingAbstractTextItem -> - OnBoardingAbstractTextItem.fromOnBoardingAbstractTextItem(onBoardingAbstractTextItem) - } - } + val (abstractPagePrefixText, abstractTexts) = mapOnBoardingAbstractToUI(onBoardingAbstract)추가 헬퍼(파일 내 임의 위치에 선언):
private fun mapOnBoardingAbstractToUI( onBoardingAbstract: com.threegap.bitnagil.domain.onboarding.model.OnBoardingAbstract ): Pair<String, List<List<OnBoardingAbstractTextItem>>> { val prefix = onBoardingAbstract.prefix val texts = onBoardingAbstract.abstractTexts.map { grp -> grp.textItems.map { OnBoardingAbstractTextItem.fromOnBoardingAbstractTextItem(it) } } return prefix to texts }
281-285: 사이드이펙트 순서: 토스트 → 화면 이동이 자연스러움
이전 화면으로 즉시 이동하면 토스트가 표시되지 않을 수 있습니다. 토스트 먼저, 네비게이션 후가 안전합니다.- sendSideEffect(sideEffect = OnBoardingSideEffect.MoveToPreviousScreen) - sendSideEffect(sideEffect = OnBoardingSideEffect.ShowToast(message = intent.message)) + sendSideEffect(sideEffect = OnBoardingSideEffect.ShowToast(message = intent.message)) + sendSideEffect(sideEffect = OnBoardingSideEffect.MoveToPreviousScreen) return null
300-321: 중복된 Abstract 매핑 재등장 — 앞선 헬퍼 재사용 권장
여기도 동일 매핑이 반복됩니다. 앞서 제안한mapOnBoardingAbstractToUI로 치환해 중복 제거하세요.- val abstractPagePrefixText = onBoardingAbstract.prefix - val abstractTexts = onBoardingAbstract.abstractTexts.map { onBoardingAbstractText -> - onBoardingAbstractText.textItems.map { onBoardingAbstractTextItem -> - OnBoardingAbstractTextItem.fromOnBoardingAbstractTextItem(onBoardingAbstractTextItem) - } - } + val (abstractPagePrefixText, abstractTexts) = mapOnBoardingAbstractToUI(onBoardingAbstract)
345-348: 비동기 작업에 async 대신 launch 사용 권장(결과 대기 불필요)
결과를await하지 않는 fire-and-forget 작업은launch가 의도가 명확합니다.Job타입 보관 패턴과도 자연스럽습니다.- loadRecommendRoutinesJob = viewModelScope.async { + loadRecommendRoutinesJob = viewModelScope.launch {
361-375: 추천 루틴 조회 실패 시 사용자 피드백/복구 경로 부재
onFailure가 빈 구현입니다. 최소 토스트 노출과 재시도/스킵 유도 등 UX 보완이 필요합니다. 현재 인텐트에는 실패용 액션이 없어 보이므로OnBoardingIntent.LoadRecommendRoutinesFailure(message: String)추가 후reduceState에서 토스트 노출 정도를 권장합니다. 원하시면 인텐트/리듀서 추가까지 패치 제안 드리겠습니다.
389-411: 추천 루틴 등록 실패 시 무시됨 — 실패 처리 및 사용자 안내 필요
registerRecommendOnBoardingRoutinesUseCase의 실패 분기에서 아무 동작도 하지 않습니다. 토스트 + 로딩 해제/재시도 유도 등 최소 실패 처리 로직을 추가해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt(11 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (2)
sendIntent(30-37)sendSideEffect(23-23)
🔇 Additional comments (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (4)
58-69: 초기 분기(NEW/RESET) 로딩 플로우 분리 명확
OnBoardingSetType으로 초기 진입을 분리한 점 좋습니다. 이후 상태에도 type을 반영해 일관성 있게 처리하고 있어 이해 용이합니다.
111-120: onBoardings 빈 응답 시 UX/안정성 검토 필요
도메인이 항상 비지 않음을 보장한다면 OK입니다. 그렇지 않다면 이후first()접근 지점에서 크래시 여지가 있으니 방어 로직이 필요합니다(아래 LoadOnBoardingSuccess 코멘트에서 구체 수정안 제시).
240-250: 추천 루틴 결과가 비어 있을 때 NEW 플로우 진행성 확인 필요
NEW에서mustSelectRecommendRoutine = true이면 빈 추천 결과일 때 다음 버튼 비활성 유지로 진행이 막힐 수 있습니다. 빈 리스트 시 자동 스킵 허용 또는 스킵 버튼 노출로 UX 보완이 필요한지 확인 부탁드립니다.
37-39: 검토 완료: Hilt 바인딩 및 AssistedFactory 사용처 문제 없음
- FetchUserProfileUseCase, GetUserOnBoardingUseCase는 각 클래스에
@Inject생성자가 선언되어 있어 Hilt가 자동으로 바인딩합니다. 별도의@Module/@Provides/@Binds정의가 필요 없습니다.@HiltViewModel(assistedFactory = OnBoardingViewModel.Factory::class)와@AssistedFactory로 선언된Factory인터페이스의 시그니처(create(onBoardingArg: OnBoardingScreenArg))는 변경되지 않았으므로, 기존 Fragment/Activity에서의 뷰모델 생성 코드에도 추가 수정이 필요 없습니다.
[ PR Content ]
온보딩 화면에 2차 UI를 반영합니다.
Related issue
Screenshot 📸
KakaoTalk_Video_2025-08-20-23-07-43.mp4
KakaoTalk_Video_2025-08-20-23-07-53.mp4
Work Description
To Reviewers 📢
Summary by CodeRabbit
신기능
개선사항
UI/스타일